package forward

import (
	"context"
	"errors"
	"gitee.com/sunny-zhangqiang/zcache/config/domain"
	"strconv"
	"time"

	"gitee.com/sunny-zhangqiang/zcache/service/base"
	"gitee.com/sunny-zhangqiang/zcache/service/loadbalance"
	"gitee.com/sunny-zhangqiang/zcache/xhttp"
)

const (
	forwardStatusTag = "forward_status"
)

type forwardStatus struct {
	// 如果对应线路的类型为IP, endpoint和line是相同的.
	// 如果对应线路的类型为域名时
	// line为对应域名的endpoint, endpoint为当前域名解析后的endpoint
	lastEndpoint *loadbalance.Endpoint
	lastLine     *loadbalance.Endpoint
	lastErr      error
	retry        int
	follow302    int
	mainLineDown bool
	backLineDown bool
}

var (
	errRetryLimit = errors.New("maximum number of retries reached")
)

func pickUpstreamServer(originConf *domain.OriginConfig, fs *forwardStatus) error {
	var ep *loadbalance.Endpoint
	var err error

	if !fs.mainLineDown {
		ep, err = originConf.GetMainEndpointPool().PickServerByRoundRobin()
		if err == nil {
			fs.lastLine = ep
			return nil
		}
		fs.mainLineDown = true
	}

	if !fs.backLineDown {
		ep, err = originConf.GetBackupEndpointPool().PickServerByRoundRobin()
		if err == nil {
			fs.lastLine = ep
			return nil
		}
		fs.backLineDown = true
	}

	if originConf.RandomPickInAllDone {
		ep, err = originConf.GetMainEndpointPool().RandomPick()
		if err == nil {
			fs.lastLine = ep
			return nil
		}
	}

	return errors.New("no avaliable upstream server")
}

func pickUpstreamServerByDomainLine(fs *forwardStatus) error {
	// 这部分代码待简化. 这里只实现功能, 未考虑代码复用性.
	v, ok := fs.lastLine.GetMeta("dns_result")
	if ok {
		res := v.(*DNSResult)
		now := time.Now().Unix()
		// Perf: 可以创建一个协程, 用于DNS预解析, 从而降低延迟.
		if now <= res.ExpireTime {
			v, ok := fs.lastLine.GetMeta("sub_endpoint_pool")
			if ok {
				pool := v.(*loadbalance.EndpointPool)
				ep, err := pool.RandomPick()
				if err != nil {
					return err
				}
				fs.lastEndpoint = ep
				return nil
			}
		}
	}

	res, err := resolver.Lookup(fs.lastLine.Addr)
	if err != nil {
		return err
	}

	epp := loadbalance.NewEndpointPool()
	v, _ = fs.lastLine.GetMeta(domain.OriginLineTag)
	lineConf := v.(*domain.OriginLine)

	for _, v := range res.Address {
		epp.AddEndpoint(loadbalance.NewEndpoint(v, fs.lastLine.Port,
			1, lineConf.MaxFails, lineConf.MaxFailedTimeoutSec))
	}

	epp.Init()

	fs.lastLine.SetMeta("sub_endpoint_pool", epp)
	fs.lastLine.SetMeta("dns_result", res)

	ep, err := epp.RandomPick()
	if err != nil {
		return err
	}
	fs.lastEndpoint = ep
	return err
}

// 待实现.
func checkErrIsTCPConnErr(err error) bool {
	return false
}

func Forward(req *base.Request) (resp *xhttp.Response, err error) {
	originConf := req.Conf.OriginConfig
	serverReq := req.ClientReq.Clone(context.Background())
	req.ServerReq = serverReq
	serverReq.URL.Scheme = "http"
	if originConf.Scheme == domain.OriginSchemeHttps {
		serverReq.URL.Scheme = "https"
	}
	serverReq.Host = req.ClientReq.Host
	// 设置回源Host
	if originConf.Host != "" {
		serverReq.Host = originConf.Host
	}

	fs := &forwardStatus{}
	req.KV[forwardStatusTag] = fs
	for {
		if fs.retry > 0 && fs.retry > originConf.MaxRetry {
			return nil, errRetryLimit
		}

		err := pickUpstreamServer(originConf, fs)
		if err != nil {
			return nil, err
		}

		v, ok := fs.lastLine.GetMeta(domain.OriginLineTag)
		if !ok {
			// FIXME: 用return error更好一些?
			panic("unreachable")
		}

		lineConf := v.(*domain.OriginLine)
		// 当Type为IP时, LastEndpoint等同与LastLine.
		// 当Type为domain时, 需要再解析一次.
		if lineConf.Type == domain.OriginLineTypeIP {
			fs.lastEndpoint = fs.lastLine
		} else if lineConf.Type == domain.OriginLineTypeDomain {
			err := pickUpstreamServerByDomainLine(fs)
			if err != nil {
				// 当前线路无可用IP, 上报fail状态
				fs.lastLine.FailBack()
				return nil, err
			}
		} else {
			return nil, errors.New("invalid origin line type")
		}

		port := fs.lastEndpoint.Port
		if port == 0 {
			port = 80
			if serverReq.URL.Scheme == "https" {
				port = 443
			}
		}

		// FIXME: 如果这里的Addr为IPv6, 需要转化为
		// 这里的结果需要保证为 "[Addr]:Port"
		// Perf: port可否实现定义为string, 从而避免大量的strconv.Itoa ?
		host := fs.lastEndpoint.Addr + ":" + strconv.Itoa(port)
		serverReq.URL.Host = host
		tp := PickTransport(host)
		resp, err := tp.RoundTrip(serverReq)
		if err != nil {
			// 检查是否为TCP连接异常, 其中包括Connect refused 和 Connect Timeout
			if checkErrIsTCPConnErr(err) {
				// 故障熔断.
				fs.lastEndpoint.FailBack()
				if fs.lastEndpoint != fs.lastLine {
					fs.lastLine.FailBack()
				}
			}
			return nil, err
		}

		// follow 302, 暂未实现.
		//if resp.Status == 302 && originConf.Follow302 {
		//}
		return resp, nil
	}
}
